//
// Created by Administrator on 2021/7/31.
// row-major in cpu memory
// to gpu convert column-major

#ifndef OPENGLLEARN_Matrix4x4_H
#define OPENGLLEARN_Matrix4x4_H

#include "Vector3.h"

class Matrix4x4
{
public:
    // ctor
    Matrix4x4() : Matrix4x4(0, 0, 0, 0,
                            0, 0, 0, 0,
                            0, 0, 0, 0,
                            0, 0, 0, 0) {}
    Matrix4x4(float m00, float m01, float m02,
              float m10, float m11, float m12,
              float m20, float m21, float m22)
              : Matrix4x4(m00, m01, m02, 0,
                          m10, m11, m12, 0,
                          m20, m21, m22, 0,
                          0, 0, 0, 1) {}
    Matrix4x4(float m00, float m01, float m02, float m03,
              float m10, float m11, float m12, float m13,
              float m20, float m21, float m22, float m23)
            : Matrix4x4(m00, m01, m02, m03,
                        m10, m11, m12, m13,
                        m20, m21, m22, m23,
                        0, 0, 0, 1) {}
    Matrix4x4(float m00, float m01, float m02, float m03,
              float m10, float m11, float m12, float m13,
              float m20, float m21, float m22, float m23,
              float m30, float m31, float m32, float m33)
              : data_{ m00, m01, m02, m03,
                       m10, m11, m12, m13,
                       m20, m21, m22, m23,
                       m30, m31, m32, m33} {}
    Matrix4x4(const Matrix4x4& rhs) = default;
    Matrix4x4(Matrix4x4&& rhs) = default;

    // functions
    inline float Determinant();
    inline void Trans();
    Matrix4x4 Transport();
    void Invert();
    Matrix4x4 Inverse();
    Matrix4x4 RotateInverse();
    Matrix4x4 ScaleInverse();
    void GetData(float* data, bool transport = false) const;

    // static functions
    static Matrix4x4 Identity()
    {
        return {
            1, 0, 0,
            0, 1, 0,
            0, 0, 1
        };
    }

    // operators
    Matrix4x4& operator= (const Matrix4x4& rhs) = default;
    Matrix4x4& operator= (Matrix4x4&& rhs) = default;

    Matrix4x4& operator+= (const Matrix4x4& rhs)
    {
        for(auto i = 0; i < 4; ++i)
        {
            for(auto j = 0; j < 4; ++j)
            {
                data_[i][j] += rhs[i][j];
            }
        }

        return *this;
    }
    Matrix4x4 operator+ (const Matrix4x4& rhs)
    {
        Matrix4x4 mat;
        for(auto i = 0; i < 4; ++i)
        {
            for(auto j = 0; j < 4; ++j)
            {
                mat[i][j] = data_[i][j] + rhs[i][j];
            }
        }

        return mat;
    }
    Matrix4x4& operator-= (const Matrix4x4& rhs)
    {
        for(auto i = 0; i < 4; ++i)
        {
            for(auto j = 0; j < 4; ++j)
            {
                data_[i][j] -= rhs[i][j];
            }
        }

        return *this;
    }
    Matrix4x4 operator- (const Matrix4x4& rhs)
    {
        Matrix4x4 mat;
        for(auto i = 0; i < 4; ++i)
        {
            for(auto j = 0; j < 4; ++j)
            {
                mat[i][j] = data_[i][j] - rhs[i][j];
            }
        }

        return mat;
    }
    Matrix4x4& operator*= (const float& rhs)
    {
        for(auto i = 0; i < 4; ++i)
        {
            for(auto j = 0; j < 4; ++j)
            {
                data_[i][j] *= rhs;
            }
        }

        return *this;
    }
    Matrix4x4 operator* (const float& rhs)
    {
        Matrix4x4 mat;
        for(auto i = 0; i < 4; ++i)
        {
            for(auto j = 0; j < 4; ++j)
            {
                mat[i][j] = data_[i][j] * rhs;
            }
        }

        return mat;
    }
    Matrix4x4& operator/= (const float& rhs)
    {
        for(auto i = 0; i < 4; ++i)
        {
            for(auto j = 0; j < 4; ++j)
            {
                data_[i][j] *= rhs;
            }
        }

        return *this;
    }
    Matrix4x4 operator/ (const float& rhs)
    {
        Matrix4x4 mat;
        for(auto i = 0; i < 4; ++i)
        {
            for(auto j = 0; j < 4; ++j)
            {
                mat[i][j] = data_[i][j] / rhs;
            }
        }

        return mat;
    }
    Vector3 operator* (const Vector3& rhs)
    {
        Vector3 vec3;
        for(auto i = 0; i < 4; ++i)
        {
            vec3[i] = data_[i][0] * rhs.x() + data_[i][1] * rhs.y() + data_[i][2] * rhs.z();
        }

        return vec3;
    }
    Matrix4x4 operator* (const Matrix4x4& rhs)
    {
        Matrix4x4 mat;

        for(auto i = 0; i < 4; ++i)
        {
            for(auto j = 0; j < 4; ++j)
            {
                mat[i][j] = data_[i][0] * rhs[0][j] +
                            data_[i][1] * rhs[1][j] +
                            data_[i][2] * rhs[2][j] +
                            data_[i][3] * rhs[3][j];
            }
        }

        return mat;
    }

    float* operator[] (int i)
    {
        return data_[i];
    }
    const float* operator[] (int i) const
    {
        return data_[i];
    }

private:
    float data_[4][4];
};

#endif //OPENGLLEARN_Matrix4x4_H
